
import { _decorator, Component, Node, Slider, Vec3, RigidBody, misc, PhysicsSystem, Quat, SphereCollider, Button, director, Input, v3, tween, Director, physics, ParticleAsset, Prefab, instantiate, MeshRenderer, v4, game, Game, geometry } from 'cc';

import { HitIndicator } from './HitIndicator';
import { tgxSceneUtil, tgxUIAlert } from '../../core_tgx/tgx';
import { UserMgr } from '../../module_basic/scripts/UserMgr';
import GameConst from '../../module_basic/scripts/config/GConst';
import { MsgHitBallComplete } from '../../module_basic/shared/protocols/worldServer/billiards/MsgHitBallComplete';
import { SceneDef } from '../../scripts/SceneDef';
import { AudioPlayer } from '../scripts/AudioPlayer';
import { BilliardsGameMgr, BilliardsGameEvent } from '../scripts/BilliardsGameMgr';
import { FixedPhysics } from '../scripts/FixedPhysics';
import { BallCollision } from './BallCollision';
import { PocketTrigger } from './PocketTrigger';
import { PocktedBalls } from './PocktedBalls';
const { ccclass, property } = _decorator;

const MAX_HIT_STRENGTH = 120;
const MAX_BALL_NUM = 16;

const BALL_RADIUS = 0.7;

const tmpV3_1 = v3();
const tmpV3_2 = v3();
const tmpV3_3 = v3();

const planes = [
    new geometry.Plane(1, 0, 0, -23.84),
    new geometry.Plane(-1, 0, 0, -23.84),
    new geometry.Plane(0, 0, 1, -11.11),
    new geometry.Plane(0, 0, -1, -11.11)
];

const hitRay = new geometry.Ray();

@ccclass('BilliardTableControl')
export class BilliardTableControl extends Component {

    //#region 编辑器序列化变量
    @property(Slider)
    powerSlider: Slider = null!;

    @property(Slider)
    directionSlider: Slider = null!;

    @property(Button)
    hitButton: Button = null!;

    @property(Node)
    cue: Node = null!;

    @property(Node)
    whiteBall: Node = null;

    @property(Node)
    indicatorWhiteBall: Node;

    @property(Node)
    beHitBalls: Node = null;
    //#endregion

    @property(Prefab)
    ballPrefab: Prefab;

    @property(Prefab)
    tablePrefab: Prefab;

    @property(Node)
    table: Node;

    @property(HitIndicator)
    hitIndicator: HitIndicator;

    @property(PocktedBalls)
    pocketedBalls: PocktedBalls;

    private _allBalls: Array<Node> = new Array<Node>(MAX_BALL_NUM);

    //#region 私有变量
    private _impulse: Vec3 = new Vec3();
    private _cueRot: Vec3 = v3();
    private _cueOffset: Vec3 = new Vec3();
    private _hitOffset: Vec3 = new Vec3();
    private _whiteballOrignalPos = v3();

    private _ballsPosData = new Array<number>(MAX_BALL_NUM * 3);
    private _ballsQuatData = new Array<number>(MAX_BALL_NUM * 4);
    //#endregion

    private _needUpdatePhysics: boolean = false;
    private _cueSyncData = null;

    //#region 组件第一次激活前执行，也就是第一次执行 update 之前触发
    start() {

        this.cue.getPosition(this._cueOffset);
        this._hitOffset.set(this._cueOffset);
        this._hitOffset.x = -7.65;
        this.cue.parent.setPosition(this.whiteBall.position);
        if (BilliardsGameMgr.inst.gameData && BilliardsGameMgr.inst.gameData.curDirection) {
            let curDir = BilliardsGameMgr.inst.gameData.curDirection;
            if (curDir) {
                this.cue.parent.setRotationFromEuler(curDir.x, curDir.y, curDir.z);
            }
        }

        this.indicatorWhiteBall.active = false;

        this._currentHitOrder = 0;
        if (BilliardsGameMgr.inst.gameData) {
            this._currentHitOrder = BilliardsGameMgr.inst.order;
        }

        for (let i = 0; i < MAX_BALL_NUM; ++i) {
            if (i == 0) {
                this._allBalls[i] = this.whiteBall;
            }
            else {
                this._allBalls[i] = this.beHitBalls.children[i - 1];
            }
        }

        this._whiteballOrignalPos.set(this._allBalls[0].worldPosition);

        if (BilliardsGameMgr.inst.isOnline) {
            let ballsData = null;
            if (BilliardsGameMgr.inst.gameData && BilliardsGameMgr.inst.gameData.ballsData) {
                ballsData = BilliardsGameMgr.inst.gameData.ballsData;
            }
            else {
                ballsData = this.getSyncData(false).ballsData;
            }
            this.doBallsDataSync(ballsData);
        }

        let data = this.getSyncData(false);
        this.resetBalls(data.ballsData);

        this.onTurnPlayerChanged();

        this.initEventListener();
    }

    resetBalls(data: { ballsPosData, ballsQuatData }) {
        if (!data) {
            return;
        }

        for (let i = this._allBalls.length - 1; i >= 0; --i) {
            this._allBalls[i].destroy();
        }

        let atlasWith = 512;
        let altasHeight = 512;
        let paddingLeft = 10;
        let paddingTop = 10;
        let gap = 4;

        let texWidth = 120;
        let texHeight = 60;
        let maxCols = 3;

        let scaleU = texWidth / atlasWith;
        let scaleV = texHeight / altasHeight;

        for (let i = 0; i < MAX_BALL_NUM; ++i) {
            let ball = instantiate(this.ballPrefab);
            ball.addComponent(BallCollision);
            this._allBalls[i] = ball;

            if (i == 0) {
                this.whiteBall = ball;
                ball.name = GameConst.hitBallNodeName;
                this.beHitBalls.parent.addChild(ball);
            }
            else {
                ball.name = 'ball-' + i;
                this.beHitBalls.addChild(ball);
            }

            this.setBallData(i, data);

            if (BilliardsGameMgr.inst.isPocketed(i)) {
                ball.active = false;
            }

            let col = i % maxCols
            let row = Math.floor(i / maxCols)
            let tx = paddingLeft + col * (texWidth + gap)
            let ty = paddingTop + row * (texHeight + gap)
            let offsetU = tx / atlasWith;
            let offsetV = ty / altasHeight;

            let meshRenderer = ball.getComponent(MeshRenderer);
            meshRenderer.setInstancedAttribute('a_tilingOffset', [scaleU, scaleV, offsetU, offsetV]);
        }

        PhysicsSystem.instance.physicsWorld.syncSceneToPhysics();
    }

    //#region 初始化物理刷新事件监听
    async initEventListener() {
        director.getScene().on(BilliardsGameEvent.CUE_CHANGED, this.onEvent_CueChanged, this);
        director.getScene().on(BilliardsGameEvent.HIT_BALL, this.onEvent_HitBall, this);
        director.getScene().on(BilliardsGameEvent.TURN_PLAYER_CHANGED, this.onTurnPlayerChanged, this);
        director.getScene().on(BilliardsGameEvent.BALL_SYNC, (data) => {
            if (BilliardsGameMgr.inst.isMyTurn) {
                return;
            }
            this.doBallsDataSync(data);
        }, this);

        director.getScene().on(FixedPhysics.EVENT_UPDATE_FINISHED, this.onEvent_FixedPhysicsUpdateFinished, this);
        director.getScene().on(PocketTrigger.EVENT_HIT_POCKET, this.onPocket, this);

        this.powerSlider.handle.node.on(Input.EventType.TOUCH_END, this.onEvent_TouchEnd, this);
        this.powerSlider.handle.node.on(Input.EventType.TOUCH_CANCEL, this.onEvent_TouchEnd, this);

        this.directionSlider.handle.node.on(Input.EventType.TOUCH_END, this.onDirectionSliderTouchEnd, this);
        this.directionSlider.handle.node.on(Input.EventType.TOUCH_CANCEL, this.onDirectionSliderTouchEnd, this);

        director.on(Director.EVENT_AFTER_UPDATE, this.onEvent_PostUpdate, this);

        director.getScene().on(BilliardsGameEvent.GAME_BEGIN, this.onGameBegin, this);

        director.getScene().on(BilliardsGameEvent.GAME_OVER, this.onGameOver, this);
    }

    private _lastProgress = 0.5;
    private _lastSlideTime = 0;
    private _slideLen = 0;
    onDirectionSliderTouchEnd() {
        this._lastProgress = 0.5;
        this.directionSlider.enabled = false;
        this.directionSlider.progress = 0.5;
        this.directionSlider.enabled = true;
        this._lastSlideTime = 0;
    }

    onDestroy() {
        director.off(Director.EVENT_AFTER_UPDATE, this.onEvent_PostUpdate, this);
    }

    hideIndicator() {
        this.hitIndicator.hideAll();
        this.indicatorWhiteBall.active = false;
    }

    dirToDegree(d: Vec3) {
        d.y = 0;
        d.normalize();
        //console.log(d);
        let degree = Math.atan(-d.z / d.x) / Math.PI * 180;
        //console.log(degree);
        if (d.x < 0) {
            degree += 180;
        }
        else {
            degree += 360;
        }
        return degree;
    }

    checkBorders(ray: geometry.Ray): { mint: number, hitPlane: geometry.Plane } {
        let mint = -1;
        let hitPlane: geometry.Plane = null;
        for (let i = 0; i < planes.length; ++i) {
            let t = geometry.intersect.rayPlane(ray, planes[i]);
            //console.log('t', t);
            if (t > 0 && (mint < 0 || t < mint)) {
                mint = t;
                hitPlane = planes[i];
            }
        }
        return { mint: mint, hitPlane: hitPlane };
    }

    computeLen(pos: Vec3, d: Vec3, len: number) {
        hitRay.o.set(pos.x, 0, pos.z);
        hitRay.d.set(d.x, 0, d.z);
        let mint = this.checkBorders(hitRay).mint;
        if (mint > 0 && mint < len) {
            len = mint;
        }
        return len;
    }

    adjustIndicator() {
        this.hitIndicator.hideAll();
        let o = this.whiteBall.position;
        let d = tmpV3_1.set(1, 0, 0);

        var radian = misc.degreesToRadians(this.cue.parent.eulerAngles.y);
        Vec3.rotateY(d, d, this.whiteBall.getComponent(SphereCollider).center, radian);

        hitRay.o.set(o.x, o.y, o.z);
        hitRay.d.set(d.x, d.y, d.z);

        d.normalize();
        //console.log(d);
        let mint = -1;
        let targetBall: Node = null;

        for (let i = 1; i < this._allBalls.length; ++i) {
            let pb = this._allBalls[i];
            if (pb.active) {
                let t = this.predict(o, d, pb.position);
                if (t >= 0 && (mint < 0 || t < mint)) {
                    mint = t;
                    targetBall = pb;
                    //console.log(t, pb.name);
                }
            }
        }

        if (mint >= 0) {
            let pos = this.indicatorWhiteBall.position;
            this.indicatorWhiteBall.active = true;
            this.indicatorWhiteBall.setPosition(o.x + d.x * mint, pos.y, o.z + d.z * mint);
            this.hitIndicator.setHitLine(this.cue.parent.eulerAngles.y, o, mint);

            let d2 = tmpV3_2;
            d2.normalize();
            Vec3.subtract(d2, targetBall.position, this.indicatorWhiteBall.position);
            let degree = this.dirToDegree(d2);
            let len = this.computeLen(targetBall.position, d2, 20);
            this.hitIndicator.setBallLine(degree, targetBall.position, len + BALL_RADIUS);

            //
            let up = tmpV3_3;
            Vec3.cross(up, d, d2);
            Vec3.cross(d, d2, up);
            d.normalize();
            len = this.computeLen(this.indicatorWhiteBall.position, d, 10);
            degree = this.dirToDegree(d);
            this.hitIndicator.setWhiteLine(degree, this.indicatorWhiteBall.position, len + BALL_RADIUS);
        }
        else {
            //check borders
            let ret = this.checkBorders(hitRay);
            let mint = ret.mint;
            let hitPlane = ret.hitPlane;
            if (mint > 0) {
                let pos = tmpV3_2.set(this.indicatorWhiteBall.position);
                pos.x = o.x + d.x * mint;
                pos.z = o.z + d.z * mint;
                this.indicatorWhiteBall.setPosition(pos);
                this.hitIndicator.setHitLine(this.cue.parent.eulerAngles.y, o, mint);

                let isInPocket = false;
                //check whether it reaches the pocket.
                for (let i = 0; i < this.table.children.length; ++i) {
                    let pocket = this.table.children[i];
                    let r = pocket.worldScale.x / 2;
                    let dx = pocket.worldPosition.x - (pos.x + d.x * BALL_RADIUS);
                    let dz = pocket.worldPosition.z - (pos.z + d.z * BALL_RADIUS);
                    if (dx * dx + dz * dz < r * r) {
                        isInPocket = true;
                        break;
                    }
                }

                this.indicatorWhiteBall.active = !isInPocket;

                //compute reflection direction.
                if (!isInPocket) {
                    //R = V - 2*dot(V,N)*N
                    let dot = -2 * Vec3.dot(d, hitPlane.n);
                    let r = tmpV3_3;
                    r.x = d.x + dot * hitPlane.n.x;
                    r.y = 0;//-d.y - dot * hitPlane.n.y;
                    r.z = d.z + dot * hitPlane.n.z;
                    r.normalize();

                    let len = this.computeLen(pos, r, 20);
                    let degree = this.dirToDegree(r);
                    this.hitIndicator.setBallLine(degree, pos, len + BALL_RADIUS);
                }
            }
            else {
                this.indicatorWhiteBall.active = false;
            }
        }
    }

    predict(o: Vec3, d: Vec3, Pb: Vec3) {
        /**
         * 设球 A 的位置为 Pa(x,y,z)，球 B 的位置为 Pb(x,y,z)
         * 当两个球相交时，有
         * dx = Pa.x - Pb.x
         * dz = Pa.z - Pb.z;
         * dx*dx + dz*dz = (2r)^2
         * => (Pa.x - Pb.x)^2 + (Pa.z - Pb.z)^ = 4r^2
         * 由于 Pa(o.x+d.x*t,_,o.z+d.z*t)
         * => (o.x+d.x*t - Pb.x)^2 + (o.z+d.z*t - Pb.z)^2 = 4r^2
         * 设 M = o.x - Pb.x, N = o.z - Pb.z
         * => (M + d.x*t)^2 + (N + d.z*t)^2 = 4r^2
         * => M^2 + d.x^2 * t^2 + 2*M*d.x*t + N^2 + d.z^2t^2 + 2*N*d.z*t - 4r^2 = 0;
         * => (d.x^2 + d.z^2)*t^2 + (2*M*d.x + 2*N*d.z)*t + M^2+N^2-4r^2
         * 若一元二次方程有解，则相交。
         * a = d.x^2 + d.z^2
         * b = 2*M*d.x + 2*N*d.z
         * c = M^2+N^2-4r^2
        */
        let M = o.x - Pb.x;
        let N = o.z - Pb.z;
        let r = BALL_RADIUS;

        let a = d.x * d.x + d.z * d.z;
        let b = 2 * M * d.x + 2 * N * d.z;
        let c = M * M + N * N - 4 * r * r;

        if (a != 0) {
            let v = b * b - 4 * a * c;
            if (v >= 0) {
                let t1 = (-b + Math.sqrt(v)) / (2 * a);
                let t2 = (-b - Math.sqrt(v)) / (2 * a);
                if (t1 * t2 <= 0) {
                    return Math.max(t1, t2);
                }
                else {
                    return Math.min(t1, t2);
                }
            }
            else {
                //console.log('无解');
            }
        }
        else if (b != 0) {
            return -c / b;
        }
        else {
            //console.log('无解');
        }
        return -1;
    }
    //#endregion

    async onGameBegin() {
        await tgxSceneUtil.loadScene(SceneDef.BILLIARDS);
        this.scheduleOnce(() => {
            tgxUIAlert.show('游戏开始');
        }, 1);
    }

    async onGameOver(data: { winner: string, reason: number }) {
        if (BilliardsGameMgr.inst.isPlayer) {
            if (data.reason == 1) {
                tgxUIAlert.show('对手认输，你赢了！').onClick(() => {
                    BilliardsGameMgr.inst.backToLobby();
                });
            }
            else {
                if (data.winner == UserMgr.inst.userId) {
                    tgxUIAlert.show('你赢了！').onClick(() => {
                        BilliardsGameMgr.inst.backToLobby();
                    });;
                }
                else {
                    tgxUIAlert.show('你输了！').onClick(() => {
                        BilliardsGameMgr.inst.backToLobby();
                    });;
                }
            }
        }
        else {
            let userInfo = await UserMgr.inst.rpc_GetUserInfo(data.winner);
            if (data.reason == 1) {
                tgxUIAlert.show('对手认输，玩家[' + userInfo.name + '] 获胜！').onClick(() => {
                    BilliardsGameMgr.inst.backToLobby();
                });
            }
            else {
                tgxUIAlert.show('玩家[' + userInfo.name + '] 获胜！').onClick(() => {
                    BilliardsGameMgr.inst.backToLobby();
                });;
            }
        }
    }

    private _newPocketedBallIds = [];
    private _hasPocketed: boolean = false;
    onPocket(node: Node) {
        for (let i = 0; i < this._allBalls.length; ++i) {
            if (i != 0 && this._allBalls[i] == node) {
                this._newPocketedBallIds.push(i);
                this._hasPocketed = true;
                if (BilliardsGameMgr.inst.gameData) {
                    this.pocketedBalls.onPocket(BilliardsGameMgr.inst.gameData.currentPlayer, i);
                }
                else {
                    this.pocketedBalls.onPocket(UserMgr.inst.userId, i);
                }
                break;
            }
        }
        node.active = false;
        AudioPlayer.playOneShot('sounds/pocket');
    }

    onEvent_TouchEnd() {
        if (this.powerSlider.progress > 0.0) {
            this.onBtnHitBallClicked();
        }
    }

    isOutOfTable(ball: Node) {
        if (!ball) {
            return false;
        }

        if (ball.active == false) {
            return true;
        }

        let pos = ball.position;
        if (pos.y < 8.4) {
            return true;
        }
        //return ball.active == false;
        return false;
    }

    //#region 物理刷新结束的回调
    onEvent_FixedPhysicsUpdateFinished() {
        this.clearPhysicsState();
        this._needUpdatePhysics = false;
        if (this.isOutOfTable(this.whiteBall)) {
            this.whiteBall.active = true;
            this.whiteBall.setWorldPosition(this._whiteballOrignalPos);
            PhysicsSystem.instance.syncSceneToPhysics();
        }

        //only need to sync when hit by player self.
        if (this._isBallMoveByHit) {
            this.syncActionToServer(false);
        }

        this._newPocketedBallIds = [];
        this._hasPocketed = false;
        this._isBallMoveByHit = false;

        if(!BilliardsGameMgr.inst.isOnline){
            this.onTurnPlayerChanged();
        }
    }
    //#endregion

    //#region 控制球杆击球力度
    onPowerChanged(slider: Slider) {
        this.hideIndicator();
        let addPosX = this._cueOffset.x * slider.progress * 0.5;
        let addPosY = this._cueOffset.y * slider.progress * 0.5;
        let newCuePos = new Vec3(this._cueOffset.x + addPosX, this._cueOffset.y + addPosY,
            this._cueOffset.z);
        this.cue.setPosition(newCuePos);
        if (BilliardsGameMgr.inst.isOnline) {
            if (!this._cueSyncData) {
                this._cueSyncData = {};
            }
            this._cueSyncData.power = { x: newCuePos.x, y: newCuePos.y, z: newCuePos.z };
        }
    }
    //#endregion

    //#region 控制球杆击球方向
    onDirectionChanged(slider: Slider) {
        if (!this._lastSlideTime || slider.progress == this._lastProgress) {
            this._lastSlideTime = Date.now();
            this._slideLen = 0;
        }
        let deltaProgress = slider.progress - this._lastProgress;
        this._slideLen += Math.abs(deltaProgress);

        let dt = Date.now() - this._lastSlideTime;
        let factor = 1.0;
        if (dt > 0) {
            let slideSpeed = this._slideLen / dt;
            //console.log(slideSpeed);
            factor = Math.max(1.0, slideSpeed / 0.0005);
        }

        let addtionalY = deltaProgress * 45 * factor;
        this._lastProgress = slider.progress;
        //var addRotationY = 720 * (slider.progress - 0.5) * 0.6;
        let curRot = this.cue.parent.eulerAngles;
        var newHitBallRot = new Vec3(curRot.x, curRot.y + addtionalY, curRot.z);
        this.cue.parent.eulerAngles = newHitBallRot;
        this.adjustIndicator();

        if (BilliardsGameMgr.inst.isOnline) {
            if (!this._cueSyncData) {
                this._cueSyncData = {};
            }
            this._cueSyncData.direction = { x: newHitBallRot.x, y: newHitBallRot.y, z: newHitBallRot.z };
        }
    }
    //#endregion

    //#region 击球
    // *击球时要隐藏 UI 界面
    // *击球时要把所有球的坐标和旋转向其它客户端同步，保证所有客户端的物理世界数据一致
    // *击球之后要立即调用固定频率、固定时长的定时器对物理世界和节点树进行同步刷新
    private _isBallMoveByHit = false;
    onBtnHitBallClicked() {
        if (this._isBallMoveByHit) {
            return;
        }
        //TODO: 把对 active 的修改变为对 position 的修改性能会更好
        //TODO: 击球后球杆消失动画需要补充
        this._currentHitOrder++;
        this.indicatorWhiteBall.active = false;

        this._isBallMoveByHit = true;
        this._impulse = new Vec3(MAX_HIT_STRENGTH * this.powerSlider.progress, 0, 0);

        this._cueRot.set(this.cue.parent.eulerAngles);
        var radian = misc.degreesToRadians(this._cueRot.y);
        Vec3.rotateY(this._impulse, this._impulse, this.whiteBall.getComponent(SphereCollider).center, radian);

        this.syncActionToServer(true);

        this.clearPhysicsState();
        let hit = tween(this.cue).call(() => {
            this.doHitBall(this._impulse);
        });

        tween(this.cue).to(0.1, { position: this._hitOffset }, { easing: 'cubicIn' }).then(hit).start();
    }
    //#endregion



    getSyncData(needImpulse: boolean) {
        for (let i = 0; i < this._allBalls.length; i++) {
            let cur = this._allBalls[i];
            let wPos = cur.worldPosition;
            let wRot = cur.worldRotation;
            this._ballsPosData[i * 3 + 0] = wPos.x;
            this._ballsPosData[i * 3 + 1] = wPos.y;
            this._ballsPosData[i * 3 + 2] = wPos.z;

            this._ballsQuatData[i * 4 + 0] = wRot.x;
            this._ballsQuatData[i * 4 + 1] = wRot.y;
            this._ballsQuatData[i * 4 + 2] = wRot.z;
            this._ballsQuatData[i * 4 + 3] = wRot.w;
        }

        let ballsData = {
            ballsPosData: this._ballsPosData,
            ballsQuatData: this._ballsQuatData,
        }
        let syncBallData = {
            ballsData: ballsData
        };
        if (needImpulse) {
            syncBallData["hitImpulse"] = { x: this._impulse.x, y: this._impulse.y, z: this._impulse.z };
        }
        return syncBallData;
    }

    //#region 向其它客户端同步小球的坐标和旋转角度
    syncActionToServer(isHit: boolean) {
        if (!BilliardsGameMgr.inst.isOnline) {
            return;
        }

        let needImpulse = isHit;
        let data = this.getSyncData(needImpulse);
        data['order'] = this._currentHitOrder;
        if (needImpulse) {
            BilliardsGameMgr.inst.sendMsg_HitBall(data);
        }
        else {
            let completeData = data as any as MsgHitBallComplete;
            if (this._currentHitOrder <= BilliardsGameMgr.inst.order) {
                return;
            }

            if (this._hasPocketed) {
                completeData.nextPlayer = BilliardsGameMgr.inst.gameData.currentPlayer;
            }
            else {
                completeData.nextPlayer = this.getNextTurnPlayer();
            }

            BilliardsGameMgr.inst.sendMsg_HitBallComplete(completeData);
        }
    }

    onEvent_HitComplete() {
        this.onTurnPlayerChanged();
    }


    onTurnPlayerChanged() {
        let isSelfTurn = BilliardsGameMgr.inst.isMyTurn || (!BilliardsGameMgr.inst.isOnline);
        if (isSelfTurn) {
            this.adjustIndicator();
        }
        this.cue.active = true;
        this.powerSlider.node.active = isSelfTurn;
        this.powerSlider.progress = 0.0;
        this.cue.parent.setPosition(this.whiteBall.position);
        this.cue.setPosition(this._cueOffset.x, this._cueOffset.y, this._cueOffset.z);
        if (BilliardsGameMgr.inst.gameData && BilliardsGameMgr.inst.cueDir) {
            this._cueRot.set(BilliardsGameMgr.inst.cueDir);
            this.cue.parent.eulerAngles = this._cueRot;
        }
        else {
            this._cueRot.set(this.cue.parent.eulerAngles);
        }
        this.directionSlider.node.active = isSelfTurn;
        this.directionSlider.progress = 0.5;
        //this.hitButton.node.active = isSelfTurn;
    }

    _currentHitOrder = 0;

    onEvent_HitBall(hitImpulse, order) {
        if (BilliardsGameMgr.inst.isMyTurn) {
            return;
        }
        this._currentHitOrder = order;

        this.clearPhysicsState();
        let hit = tween(this.cue).call(() => {
            this.doHitBall(hitImpulse);
        });
        tween(this.cue).to(0.1, { position: this._hitOffset }, { easing: 'cubicIn' }).then(hit).start();
    }


    getNextTurnPlayer() {
        let playerList = BilliardsGameMgr.inst.gameData.players;
        for (let i = 0; i < playerList.length; ++i) {
            let p = playerList[i];
            if (p.uid == BilliardsGameMgr.inst.gameData.currentPlayer) {
                let nextIdx = (i + 1) % playerList.length;
                let p = playerList[nextIdx];
                return p.uid;
            }
        }
        return playerList[0].uid;
    }

    doHitBall(hitImpulse: Vec3) {

        /*
        //we should reset the physics engine and rebuild the physics objects in online mode.
        if (BilliardsGameMgr.inst.gameData) {
            let t = Date.now();
            let cur = physics.selector.id;
            physics.selector.switchTo('builtin');
            physics.selector.switchTo(cur);
            let cost = Date.now() - t;
            console.log('restart physics engine:' + cost + 'ms');

            t = Date.now();
            this.resetBalls(BilliardsGameMgr.inst.gameData.ballsData);
            this.table.destroy();
            this.table = instantiate(this.tablePrefab);
            director.getScene().addChild(this.table);
            cost = Date.now() - t;
            console.log('recreate physics objects:' + cost + 'ms');
        }
        */
        if (BilliardsGameMgr.inst.isOnline) {
            this.doBallsDataSync(BilliardsGameMgr.inst.gameData.ballsData);
        }
        
        this.cue.active = false;
        this.powerSlider.node.active = false;
        this.directionSlider.node.active = false;

        let impulse = new Vec3(hitImpulse.x, 0, hitImpulse.z);
        if ((!BilliardsGameMgr.inst.isOnline || BilliardsGameMgr.inst.isMyTurn)) {

            this.whiteBall.getComponent(RigidBody).applyImpulse(impulse);

            FixedPhysics.reset();
            this._needUpdatePhysics = true;
        }

        let volume = impulse.length() / MAX_HIT_STRENGTH;
        AudioPlayer.playOneShot('sounds/cue_hit', volume);
    }

    checkGameOver() {
        let pocketedNum = 0;
        for (let i = 1; i < this._allBalls.length; ++i) {
            if (this._allBalls[i].active == false) {
                pocketedNum++;
            }
        }
        return pocketedNum >= MAX_BALL_NUM - 1;
    }

    clearPhysicsState() {
        this.whiteBall.getComponent(RigidBody).clearState();
        for (let i = 0; i < this.beHitBalls.children.length; ++i) {
            let rg = this.beHitBalls.children[i].getComponent(RigidBody).clearState();
        }
        PhysicsSystem.instance.physicsWorld.syncSceneToPhysics();
    }

    setBallData(i: number, data: { ballsPosData: Array<number>, ballsQuatData: Array<number> }) {
        let ball = this._allBalls[i];
        let x = data.ballsPosData[i * 3];
        let y = data.ballsPosData[i * 3 + 1];
        let z = data.ballsPosData[i * 3 + 2];
        ball.setWorldPosition(x, y, z);

        let rx = data.ballsQuatData[i * 4];
        let ry = data.ballsQuatData[i * 4 + 1];
        let rz = data.ballsQuatData[i * 4 + 2];
        let rw = data.ballsQuatData[i * 4 + 3];
        ball.setWorldRotation(rx, ry, rz, rw);

        if (BilliardsGameMgr.inst.isPocketed(i)) {
            ball.active = false;
        }
    }

    doBallsDataSync(data: { ballsPosData: Array<number>, ballsQuatData: Array<number> }) {
        for (let i = 0; i < MAX_BALL_NUM; ++i) {
            this.setBallData(i, data);
        }
    }

    //#region 同步球杆的位置和方向
    private onEvent_CueChanged(data) {
        if (BilliardsGameMgr.inst.isMyTurn) {
            return;
        }
        if (data.power) {
            this.cue.setPosition(data.power);
        }
        if (data.direction) {
            this.cue.parent.eulerAngles = data.direction;
        }
    }
    //#endregion
    private _lastSyncTime = 0;
    update(dt: number) {
        this._lastSyncTime += dt;
        if (this._lastSyncTime > 0.1) {
            if (this._cueSyncData) {
                BilliardsGameMgr.inst.sendMsg_AdjustCue(this._cueSyncData);
                this._cueSyncData = null;
            }
            this._lastSyncTime = 0;
        }
    }

    private _isGameOver = false;
    private _fixedDt = Math.floor(1.0 / GameConst.MAX_FPS * 10000) / 10000;
    onEvent_PostUpdate() {
        if (this._needUpdatePhysics) {
            FixedPhysics.fixedUpdate(this._fixedDt);
            if (!this._isGameOver) {
                let isGameOver = this.checkGameOver();
                if (BilliardsGameMgr.inst.isOnline) {
                    let data = this.getSyncData(false) as any;
                    data.newPocketedBalls = this._newPocketedBallIds;
                    this._newPocketedBallIds = [];
                    if (isGameOver) {
                        data.gameOver = true;
                    }
                    BilliardsGameMgr.inst.sendMsg_BallsDataSync(data);
                }
                else if(isGameOver) {
                    tgxUIAlert.show('你赢了，再来一局？', true).onClick(b => {
                        if (b) {
                            tgxSceneUtil.reloadScene();
                        }
                        else {
                            BilliardsGameMgr.inst.backToLobby();
                        }
                    });
                }
                this._isGameOver = isGameOver;
            }
        }

        if (BallCollision.collideWithBalls > 0) {
            AudioPlayer.playOneShot('sounds/ball_hit_ball', BallCollision.collideWithBalls / 100.0);
        }
        BallCollision.collideWithBalls = 0.0;
    }
}